#include "Rp2WindowApplication.h"

using namespace Rp2;

//---------------------------------------------------------------------------------------------------
WindowApplication::WindowApplication(const tCHAR* acWindowTitle, int iXPosition,
	int iYPosition, int iWidth, int iHeight)
	:
	m_acWindowTitle(acWindowTitle),
	m_iXPosition(iXPosition),
	m_iYPosition(iYPosition),
	m_iWidth(iWidth),
	m_iHeight(iHeight)	
{
	// camera motion
	m_akAxisInWorld[0] = Vector3f::ZERO;
	m_akAxisInWorld[1] = Vector3f::ZERO;
	m_akAxisInWorld[2] = Vector3f::ZERO;
	m_bCameraMoveable = false;
	m_fTrnSpeed = 0.0f;
	m_fRotSpeed = 0.0f;
	m_fTrnSpeedFactor = 0.0f;
	m_fRotSpeedFactor = 0.0f;
	m_bUArrowPressed = false;
	m_bDArrowPressed = false;
	m_bLArrowPressed = false;
	m_bRArrowPressed = false;
	m_bTLArrowPressed = false;
	m_bTRArrowPressed = false;

    // Default buffer parameters.  These may be overridden by the derived
    // class constructor.  Current hardware seems to pack a 24-bit depth
    // buffer and 8-bit stencil buffer into a 32-bit buffer, so I suggest
    // not overriding these.  Perhaps the only one you should is the
    // multisampling value.
    m_eFormat = FrameBuffer::FT_FORMAT_RGBA;
    m_eDepth = FrameBuffer::DT_DEPTH_24;
    m_eStencil = FrameBuffer::ST_STENCIL_8;
    m_eBuffering = FrameBuffer::BT_BUFFERED_DOUBLE;
    m_eMultisampling = FrameBuffer::MT_SAMPLING_NONE;

    // object motion
    m_iDoRoll = 0;
    m_iDoYaw = 0;
    m_iDoPitch = 0;
    m_spkMotionObject = 0;
    m_fXTrack0 = 0.0f;
    m_fYTrack0 = 0.0f;
    m_fXTrack1 = 0.0f;
    m_fYTrack1 = 0.0f;
    m_bUseTrackBall = true;
    m_bTrackBallDown = false;

	m_fCamAngleX = 0.0f;
	m_fCamAngleY = 0.0f;
	m_fNewCamAngleX = 0.0f;
	m_fNewCamAngleY = 0.0f;

    // performance measurements
    m_dLastTime = -1.0;
    m_dAccumulatedTime = 0.0;
    m_dFrameRate = 0.0;
    m_iFrameCount = 0;
    m_iAccumulatedFrameCount = 0;
    m_iTimer = 30;
    m_iMaxTimer = 30;
}
//---------------------------------------------------------------------------------------------------
WindowApplication::~WindowApplication()
{
	;
}
//---------------------------------------------------------------------------------------------------
int WindowApplication::Run()
{
	// Static type cast for window application
	WindowApplication* pkTheApp = (WindowApplication*)TheApplication;
	// Go to the entry point defined in derived class (WglApplication)
	return pkTheApp->Main();
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::InitRenderStates()
{
	// Implement by derived class.
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnInitialize()
{
	m_spkCamera = new Camera;
	m_pkWglRenderer->SetCamera(m_spkCamera);
	m_spkMotionObject = 0;
	return true;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::OnTerminate()
{
	m_pkWglRenderer->SetCamera(0);
	m_spkCamera = 0;
	m_spkMotionObject = 0;
	delete m_pkWglRenderer;
	m_pkWglRenderer = 0;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::OnIdle()
{
	// Implement by derived class.
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::OnDisplay()
{
	
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::OnResize(int iWidth, int iHeight)
{
	m_iWidth = iWidth;
	m_iHeight = iHeight;
	if (m_pkWglRenderer)
	{
		m_pkWglRenderer->Resize(iWidth, iHeight);
	}
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnKeyDown(unsigned char ucKey, int iXPos, int iYPos)
{
	if (m_bCameraMoveable)
	{
		if (ucKey == KEY_W)
		{
			m_bUArrowPressed = true;
			return true;
		}
		if (ucKey == KEY_S)
		{
			m_bDArrowPressed = true;
			return true;
		}
		if (ucKey == KEY_A)
		{
			m_bLArrowPressed = true;
			return true;
		}
		if (ucKey == KEY_D)
		{
			m_bRArrowPressed = true;
			return true;
		}
		if (ucKey == KEY_Q)
		{
			m_bTLArrowPressed = true;
			return true;
		}
		if (ucKey == KEY_E)
		{
			m_bTRArrowPressed = true;
			return true;
		}
	}
	return false;
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnKeyUp(unsigned char ucKey, int iXPos, int iYPos)
{
	if (m_bCameraMoveable)
	{
		if (ucKey == KEY_Wu)
		{
			m_bUArrowPressed = false;
			return true;
		}
		if (ucKey == KEY_Su)
		{
			m_bDArrowPressed = false;
			return true;
		}
		if (ucKey == KEY_Au)
		{
			m_bLArrowPressed = false;
			return true;
		}
		if (ucKey == KEY_Du)
		{
			m_bRArrowPressed = false;
			return true;
		}
		if (ucKey == KEY_Qu)
		{
			m_bTLArrowPressed = false;
			return true;
		}
		if (ucKey == KEY_Eu)
		{
			m_bTRArrowPressed = false;
			return true;
		}
	}
	return false;
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnSpecialKeyDown(int iKey, int iXPos, int iYPos)
{
	if (m_bCameraMoveable)
	{
		if (iKey == KEY_LEFT_ARROW)
		{
			m_bLArrowPressed = true;
			return true;
		}
		if (iKey == KEY_RIGHT_ARROW)
		{
			m_bRArrowPressed = true;
			return true;
		}
		if (iKey == KEY_UP_ARROW)
		{
			m_bUArrowPressed = true;
			return true;
		}
		if (iKey == KEY_DOWN_ARROW)
		{
			m_bDArrowPressed = true;
			return true;
		}
	}

	return false;
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnSpecialKeyUp(int iKey, int iXPos, int iYPos)
{
	if (m_bCameraMoveable)
	{
		if (iKey == KEY_LEFT_ARROW)
		{
			m_bLArrowPressed = false;
			return true;
		}
		if (iKey == KEY_RIGHT_ARROW)
		{
			m_bRArrowPressed = false;
			return true;
		}
		if (iKey == KEY_UP_ARROW)
		{
			m_bUArrowPressed = false;
			return true;
		}
		if (iKey == KEY_DOWN_ARROW)
		{
			m_bDArrowPressed = false;
			return true;
		}
	}

	return false;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::OnDisplayBackBuffer()
{
	if (m_pkWglRenderer)
	{
		m_pkWglRenderer->DisplayBackBuffer();
	}
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::InitializeCameraMotion(float fTrnSpeed, float fRotSpeed,
	float fTrnSpeedFactor /* = 2.0f */, float fRotSpeedFactor /* = 2.0f */)
{
	m_bCameraMoveable = true;

	m_fTrnSpeed = fTrnSpeed;
	m_fRotSpeed = fRotSpeed;
	m_fTrnSpeedFactor = fTrnSpeedFactor;
	m_fRotSpeedFactor = fRotSpeedFactor;

	m_akAxisInWorld[0] = m_spkCamera->GetRVector();
	m_akAxisInWorld[1] = m_spkCamera->GetUVector();
	m_akAxisInWorld[2] = m_spkCamera->GetDVector();
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::MoveCamera()
{
	if (!m_bCameraMoveable)
	{
		return false;
	}
	bool bMoved = false;
	
	if (m_bUArrowPressed)
	{
		MoveForward();
		bMoved = true;
	}
	if (m_bDArrowPressed)
	{
		MoveBackward();
		bMoved = true;
	}
	if (m_bTLArrowPressed)
	{
		MoveLeft();
		bMoved = true;
	}
	if (m_bTRArrowPressed)
	{
		MoveRight();
		bMoved = true;
	}
	if (m_bLArrowPressed)
	{
		TurnLeft();
		bMoved = true;
	}
	if (m_bRArrowPressed)
	{
		TurnRight();
		bMoved = true;
	}


	return bMoved;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::MoveForward()
{
	Vector3f kLoc = m_spkCamera->GetLocation();
	kLoc += m_fTrnSpeed * m_akAxisInWorld[2];
	m_spkCamera->SetLocation(kLoc);
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::MoveBackward()
{
	Vector3f kLoc = m_spkCamera->GetLocation();
	kLoc -= m_fTrnSpeed * m_akAxisInWorld[2];
	m_spkCamera->SetLocation(kLoc);
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::MoveLeft()
{
	Vector3f kLoc = m_spkCamera->GetLocation();
	kLoc -= m_fTrnSpeed * m_akAxisInWorld[0];
	m_spkCamera->SetLocation(kLoc);
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::MoveRight()
{
	Vector3f kLoc = m_spkCamera->GetLocation();
	kLoc += m_fTrnSpeed * m_akAxisInWorld[0];
	m_spkCamera->SetLocation(kLoc);
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::TurnLeft()
{
	Matrix3f kMat(m_akAxisInWorld[1], -m_fRotSpeed);
	m_akAxisInWorld[0] = kMat*m_akAxisInWorld[0]; //r
	m_akAxisInWorld[2] = kMat*m_akAxisInWorld[2]; //d

	Vector3f kRVector = kMat*m_spkCamera->GetRVector();
	Vector3f kUVector = kMat*m_spkCamera->GetUVector();
	Vector3f kDVector = kMat*m_spkCamera->GetDVector();

	m_spkCamera->SetAxes(kRVector,kUVector,kDVector);
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::TurnRight()
{
	Matrix3f kMat(m_akAxisInWorld[1], m_fRotSpeed);
	m_akAxisInWorld[0] = kMat*m_akAxisInWorld[0]; //r
	m_akAxisInWorld[2] = kMat*m_akAxisInWorld[2]; //d

	Vector3f kRVector = kMat*m_spkCamera->GetRVector();
	Vector3f kUVector = kMat*m_spkCamera->GetUVector();
	Vector3f kDVector = kMat*m_spkCamera->GetDVector();

	m_spkCamera->SetAxes(kRVector,kUVector,kDVector);
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnMouseClick(int iButton, int iState, int iX, int iY,
									 unsigned int uiModifiers)
{
	if (!m_bUseTrackBall
	||	iButton != MOUSE_LEFT_BUTTON
	||	!m_spkMotionObject)
	{
		return false;
	}

	//float fMult = 1.0f/(m_iWidth >= m_iHeight ? m_iHeight : m_iWidth);
	float fMult = 1.0f/(m_iWidth >= m_iHeight ? m_iWidth : m_iHeight);

	if (iState == MOUSE_DOWN)
	{
		// get the starting point
		m_bTrackBallDown = true;
		m_kSaveRotate = m_spkMotionObject->Local.GetRotate();
		m_fXTrack0 = (2*iX - m_iWidth)*fMult;
		m_fYTrack0 = (2*(m_iHeight-1-iY)-m_iHeight)*fMult;

		m_fMouseX0 = (2*iX - m_iWidth)*fMult;
		m_fMouseY0 = (2*(m_iHeight-1-iY)-m_iHeight)*fMult;
		
		m_kSaveLocation = m_spkCamera->GetLocation();
		m_kSaveRVector	= m_spkCamera->GetRVector();
		m_fSaveDistance = m_kSaveLocation.Length();
		Vector3f vDir = m_kSaveLocation;
		vDir.Normalize();
		m_fCamAngleX = Mathf::ACos(vDir.Dot(Vector3f::UNIT_Z));
		m_fCamAngleX = vDir.X() > 0 ? m_fCamAngleX : -m_fCamAngleX;
		m_fCamAngleY = Mathf::ACos(vDir.Dot(Vector3f::UNIT_Y));
	}
	else
	{
		m_bTrackBallDown = false;
	}

	return true;
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::OnMotion(int iButton, int iX, int iY, unsigned int uiModifiers)
{
    if (!m_bUseTrackBall
    ||  iButton != MOUSE_LEFT_BUTTON
    ||  !m_bTrackBallDown
    ||  !m_spkMotionObject)
    {
        return false;
    }

    // get the ending point
    //float fMult = 1.0f/(m_iWidth >= m_iHeight ? m_iHeight : m_iWidth);
    float fMult = 1.0f/(m_iWidth >= m_iHeight ? m_iWidth : m_iHeight);
	m_fXTrack1 = (2*iX-m_iWidth)*fMult;
    m_fYTrack1 = (2*(m_iHeight-1-iY)-m_iHeight)*fMult;

	m_fMouseX1 = (2*iX-m_iWidth)*fMult;
	m_fMouseY1 = (2*(m_iHeight-1-iY)-m_iHeight)*fMult;

    // update the object's local rotation
    RotateTrackBall(m_fXTrack0,m_fYTrack0,m_fXTrack1,m_fYTrack1);
    return true;
}
//---------------------------------------------------------------------------------------------------
// object motion
//---------------------------------------------------------------------------------------------------
void WindowApplication::InitializeObjectMotion(Spatial* pkMotionObject)
{
	m_spkMotionObject = pkMotionObject;
}
//---------------------------------------------------------------------------------------------------
bool WindowApplication::MoveObject()
{
    // The coordinate system in which the rotations are applied is that of
    // the object's parent, if it has one.  The parent's world rotation
    // matrix is R, of which the columns are the coordinate axis directions.
    // Column 0 is "direction", column 1 is "up", and column 2 is "right".
    // If the object does not have a parent, the world coordinate axes are
    // used, in which case the rotation matrix is I, the identity.  Column 0
    // is (1,0,0) and is "direction", column 1 is (0,1,0) and is "up", and
    // column 2 is (0,0,1) and is "right".  This choice is consistent with
    // the use of rotations in the Camera and Light classes to store
    // coordinate axes.
    //
    // Roll is about the "direction" axis, yaw is about the "up" axis, and
    // pitch is about the "right" axis.

    if (!m_bCameraMoveable || !m_spkMotionObject)
    {
        return false;
    }

    // Check if the object has been moved by the virtual trackball.
    if (m_bTrackBallDown)
    {
        return true;
    }
	
	return false;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::RotateTrackBall(float fX0, float fY0, float fX1, float fY1)
{
    if ((fX0 == fX1 && fY0 == fY1) || !m_spkCamera)  // nothing to rotate
        return;

    // get first vector on sphere
    float fLength = Mathf::Sqrt(fX0*fX0+fY0*fY0), fInvLength, fZ0, fZ1;
    if (fLength > 1.0f)
    {
        // outside unit disk, project onto it
        fInvLength = 1.0f/fLength;
        fX0 *= fInvLength;
        fY0 *= fInvLength;
        fZ0 = 0.0f;
    }
    else
    {
        // compute point (x0,y0,z0) on negative unit hemisphere
        fZ0 = 1.0f - fX0*fX0 - fY0*fY0;
        fZ0 = (fZ0 <= 0.0f ? 0.0f : Mathf::Sqrt(fZ0));
    }
    fZ0 *= -1.0f;

    // use camera world coordinates, order is (R,U,D), so point is (x,y,z)
    Vector3f kVec0(fX0,fY0,fZ0);

    // get second vector on sphere
    fLength = Mathf::Sqrt(fX1*fX1+fY1*fY1);
    if (fLength > 1.0f)
    {
        // outside unit disk, project onto it
        fInvLength = 1.0f/fLength;
        fX1 *= fInvLength;
        fY1 *= fInvLength;
        fZ1 = 0.0f;
    }
    else
    {
        // compute point (x1,y1,z1) on negative unit hemisphere
        fZ1 = 1.0f - fX1*fX1 - fY1*fY1;
        fZ1 = (fZ1 <= 0.0f ? 0.0f : Mathf::Sqrt(fZ1));
    }
    fZ1 *= -1.0f;

    // use camera world coordinates, order is (R,U,D), so point is (x,y,z)
    Vector3f kVec1(fX1,fY1,fZ1);

    // create axis and angle for the rotation
    Vector3f kAxis = kVec0.Cross(kVec1);
    float fDot = kVec0.Dot(kVec1);
    float fAngle;
    if (kAxis.Normalize() > Mathf::ZERO_TOLERANCE)
    {
        fAngle = Mathf::ACos(kVec0.Dot(kVec1)); 
    }
    else  // vectors are parallel
    {
        if (fDot < 0.0f)
        {
            // rotated pi radians
            fInvLength = Mathf::InvSqrt(fX0*fX0+fY0*fY0);
            kAxis.X() = fY0*fInvLength;
            kAxis.Y() = -fX0*fInvLength;
            kAxis.Z() = 0.0f;
            fAngle = Mathf::PI;
        }
        else
        {
            // rotation by zero radians
            kAxis = Vector3f::UNIT_X;
            fAngle = 0.0f;
        }
    }

    // Compute the world rotation matrix implied by trackball motion.  The
    // axis vector was computed in camera coordinates.  It must be converted
    // to world coordinates.  Once again, I use the camera ordering (R,U,D).
    Vector3f kWorldAxis =
        kAxis.X()*m_spkCamera->GetRVector() +
        kAxis.Y()*m_spkCamera->GetUVector() +
        kAxis.Z()*m_spkCamera->GetDVector();

    Matrix3f kTrackRotate(kWorldAxis,fAngle);

    // Compute the new local rotation.  If the object is the root of the
    // scene, the new rotation is simply the *incremental rotation* of the
    // trackball applied *after* the object has been rotated by its old
    // local rotation.  If the object is not the root of the scene, you have
    // to convert the incremental rotation by a change of basis in the
    // parent's coordinate space. 
    const Spatial* pkParent = m_spkMotionObject->GetParent();
    Matrix3f kLocalRot;
    if (pkParent)
    {
		// Y = X * S * Rw = X * S * Rl * Rp;
		// Z = Y * Q = X * S * (Rl * Rp * Q);
		// Preserving the parent rotation, let Rl' * Rp = Rl*Rp*Q;
		// Rl' = Rl * Rp * Q * Rp^t.
        const Matrix3f& rkPRotate = pkParent->World.GetRotate();
		kLocalRot = m_kSaveRotate*rkPRotate*kTrackRotate.TimesTranspose(rkPRotate);
	}
    else
    {
		kLocalRot = m_kSaveRotate*kTrackRotate;
    }
    kLocalRot.Orthonormalize();
    //m_spkMotionObject->Local.SetRotate(kLocalRot);

	// get vector to origin
	Matrix3f rotMX, rotMY;
	m_fNewCamAngleX = m_fCamAngleX + fX1 - fX0;
	m_fNewCamAngleY = m_fCamAngleY + fY0 - fY1;
	LOG(TC("%f %f"), m_fNewCamAngleX, m_fNewCamAngleY);
	rotMY.FromAxisAngle(Vector3f::UNIT_Y, fX1 - fX0);
	rotMX.FromAxisAngle(m_spkCamera->GetRVector(), fY0 - fY1);
	Quaternionf q;
	q.FromAxisAngle(Vector3f::UNIT_Y, fX1 - fX0);
	Vector3f vNewLocation = m_spkCamera->GetLocation();
	vNewLocation = m_kSaveLocation * rotMX;// * rotMY;
// 	vNewLocation.X() = Mathf::Sin(m_fNewCamAngleX)*Mathf::Cos(m_fNewCamAngleY)*m_fSaveDistance;
// 	vNewLocation.Y() = Mathf::Sin(m_fNewCamAngleY)*m_fSaveDistance;
// 	vNewLocation.Z() = -Mathf::Cos(m_fNewCamAngleX)*Mathf::Cos(m_fNewCamAngleY)*m_fSaveDistance;
// 	LOG(TC("%f %f %f"), vNewLocation.X(), vNewLocation.Y(), vNewLocation.Z());
	// rotate to new location
	//Vector3f vNewLocation = m_kSaveLocation * rotMY * rotMX;
	m_spkCamera->LocationLookAt(vNewLocation, Vector3f::ZERO);
	//m_spkCamera->SetLocation(vNewLocation);
	//LOG(TC("%f %f %f"), vNewLocation.X(), vNewLocation.Y(), vNewLocation.Z());


}
//---------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------
// performance measurements
//---------------------------------------------------------------------------------------------------
void WindowApplication::ResetTime()
{
	m_dLastTime = -1.0;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::MeasureTime()
{
	if (m_dLastTime == -1.0)
	{
		m_dLastTime = System::GetTime();
		m_dAccumulatedTime = 0.0;
        m_dFrameRate = 0.0;
        m_iFrameCount = 0;
        m_iAccumulatedFrameCount = 0;
		m_iTimer = m_iMaxTimer;
	}

    // accumulate the time only when the miniature time allows it
    //if (--m_iTimer == 0)
	//{
		double dCurrentTime = System::GetTime();
		double dDelta = dCurrentTime - m_dLastTime;
		m_dLastTime = dCurrentTime;
		m_dAccumulatedTime += dDelta;
		m_iAccumulatedFrameCount += m_iFrameCount;
		m_iFrameCount = 0;
		m_iTimer = m_iMaxTimer;
	//}
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::UpdateFrameCount()
{
	m_iFrameCount++;
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::FrameRateControl(int iFrameRate)
{
	double dCurrentTime = System::GetTime();
	double dDelta = dCurrentTime - m_dLastTime;
	double dTimePerFrame = 1.0 / iFrameRate;

	if(dDelta < dTimePerFrame)
	{
		dDelta = dTimePerFrame - dDelta;
		//Sleep(dDelta * 1000.0);
	}
}
//---------------------------------------------------------------------------------------------------
void WindowApplication::DrawFrameRate(int iX, int iY, const ColorRGBA& rkColor)
{
	if (m_dAccumulatedTime > 0.0)
	{
		m_dFrameRate = m_iAccumulatedFrameCount/m_dAccumulatedTime;
	}
	else
	{
		m_dFrameRate = 0.0;
	}

	const size_t uiSize = 256;
	char acMessage[uiSize];
	sprintf_s(acMessage, "fps: %.1lf", m_dFrameRate);
	m_pkWglRenderer->Draw(iX, iY, rkColor, acMessage);
}
//---------------------------------------------------------------------------------------------------